﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Win32;
using System.IO;
using System.Xml.Schema;
using System.Xml;
using System.Collections;

namespace CompletionData
{
    public static class DataManager
    {
        private static Dictionary<string, List<TaskInfo>> tasksCache = new Dictionary<string, List<TaskInfo>>();

        static DataManager()
        {            
            XmlSchemaElement root = GetRootXmlSchemaElement();
            if (root != null)
                MakeCacheData("/", new List<XmlSchemaElement> { root });
        }

        public static TaskInfo GetTask(string key, string taskName)
        {
            if (string.IsNullOrWhiteSpace(key))
                throw new ArgumentNullException();
            if (string.IsNullOrWhiteSpace(taskName))
                throw new ArgumentNullException();

            List<TaskInfo> tasks = GetTasks(key);
            if (tasks == null || tasks.Count <= 0)
                return null;
            return tasks.Find(t => string.Equals(t.Name, taskName, StringComparison.CurrentCultureIgnoreCase));
        }

        public static List<PropertyInfo> GetProperties(string key, string taskName)
        {
            if (string.IsNullOrWhiteSpace(key))
                throw new ArgumentNullException();
            if (string.IsNullOrWhiteSpace(taskName))
                throw new ArgumentNullException();

            TaskInfo task = GetTask(key, taskName);
            List<PropertyInfo> result = new List<PropertyInfo>();
            if (task == null)
                return result;
            return task.Properties;
        }

        public static List<TaskInfo> GetTasks(string key)
        {
            if (string.IsNullOrWhiteSpace(key))
                throw new ArgumentNullException();

            if (tasksCache.ContainsKey(key))
                return tasksCache[key];
            else
            {
                MakeCacheData(key);
                if (tasksCache.ContainsKey(key))
                    return tasksCache[key];
                else
                    return null;
            }
        }

        private static void MakeCacheData(string key)
        {
            if (string.IsNullOrWhiteSpace(key))
                throw new ArgumentNullException();

            if (tasksCache.ContainsKey(key))
                return;

            List<XmlSchemaElement> foundElements = FindXmlSchemaElements(key);
            if (foundElements == null || foundElements.Count <= 0)
                return;
            MakeCacheData(key, foundElements);
        }

        private static void MakeCacheData(string key, List<XmlSchemaElement> elems)
        {
            if (string.IsNullOrWhiteSpace(key))
                throw new ArgumentNullException();
            if (elems == null || elems.Count <= 0)
                throw new ArgumentNullException();

            List<TaskInfo> tasks = new List<TaskInfo>();
            foreach (XmlSchemaElement elem in elems)
            {
                List<XmlSchemaAttribute> attrs = GetAttributes(elem);
                if (attrs != null)
                    attrs.Sort((a, b) => string.Compare(a.Name, b.Name));
            
                List<PropertyInfo> props = new List<PropertyInfo>();
                if (attrs != null)
                    attrs.ForEach(attr =>
                    {
                        PropertyInfo pi = new PropertyInfo();
                        pi.Name = attr.Name;
                        pi.Use = attr.Use;
                        props.Add(pi);
                    });
                TaskInfo task = new TaskInfo();
                task.Name = elem.Name;
                task.Properties = props;
                tasks.Add(task);
            }
            tasksCache.Add(key, tasks);
        }

        private static List<XmlSchemaElement> FindXmlSchemaElements(string key)
        {
            if (string.IsNullOrWhiteSpace(key))
                throw new ArgumentNullException();

            key = key.Trim(' ', '/');
            string[] taskNames = key.Split('/');
            if (taskNames.Length <= 0)
                return null;

            string schemaFilePath = GetMSBuildShemaPath();
            XmlSchema schema = GetXmlSchema(schemaFilePath);
            XmlSchemaElement currentElement = GetRootXmlSchemaElement();
            return MSBuildXmlSchema.GetDescendantElement(schema, currentElement, taskNames);
        }

        public static XmlSchemaElement GetRootXmlSchemaElement()
        {
            string schemaFilePath = GetMSBuildShemaPath();
            XmlSchema schema = GetXmlSchema(schemaFilePath);
            IEnumerator enumerator = schema.Elements.Values.GetEnumerator();
            XmlSchemaElement root = null;
            if (enumerator.MoveNext())
                root = enumerator.Current as XmlSchemaElement;

            return root;
        }

        private static List<XmlSchemaAttribute> GetAttributes(XmlSchemaElement elem)
        {
            if (elem == null)
                throw new ArgumentNullException();

            XmlSchemaComplexType complexType = elem.ElementSchemaType as XmlSchemaComplexType;
            if (complexType == null)
                return null;

            List<XmlSchemaAttribute> result = new List<XmlSchemaAttribute>();
            foreach (XmlSchemaAttribute attr in complexType.AttributeUses.Values)
                result.Add(attr);

            return result;
        }

        public static XmlSchema GetXmlSchema(string schemaFilePath)
        {
            if (string.IsNullOrWhiteSpace(schemaFilePath))
                throw new ArgumentNullException();

            if (!File.Exists(schemaFilePath))
                return null;

            XmlSchema result = null;
            using (XmlTextReader reader = new XmlTextReader(schemaFilePath))
            {
                XmlSchema schema = XmlSchema.Read(reader, null);
                XmlSchemaSet set = new XmlSchemaSet();
                set.Add(schema);
                set.Compile();
                result = schema;
            }
            return result;
        }

        public static string GetMSBuildShemaPath()
        {
            ReservedProperties propsInstance = ReservedProperties.GetInstance();
            string schemaDir = propsInstance.GetProperty("MSBuildToolsPath");
            if (schemaDir == null)
                return null;

            string schemaFilePath = Path.Combine(schemaDir, "MSBuild", "Microsoft.Build.Commontypes.xsd");
            return schemaFilePath;
        }

    }
}
